Analysis of the dataset of discovered exoplanets (2021) - Group D (Teggi, Verdolin)¶

link to the dataset: https://www.kaggle.com/datasets/shivamb/all-exoplanets-dataset?resource=download

Data import, filtering and preparation for analysis¶

In [1]:
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.subplots as sp
import plotly.graph_objects as go

# Load the dataset
dataset_path = "data/exoplanets/all_exoplanets_2021.csv"
exoplanet_data = pd.read_csv(dataset_path)

# Filter the columns from the total 23
selected_columns = [
    "Planet Name",                   # string (name of the planet)
    "Planet Host",                   # string (name of the star system)
    "Num Stars",                     # int (number of stars in that star system)
    "Num Planets",                   # int (number of planets discovered in that star system)
    "Discovery Method",              # string (method used)
    "Discovery Year",                # int (year of discovery)
    "Discovery Facility",            # string (observatory)
    "Orbital Period Days",           # double (orbital period of the planet around the star [earth days])
    "Orbit Semi-Major Axis",         # double (greater distance from the star [AU (149 597 870 700 m)])
    "Mass",                          # double (mass of the planet [earth mass])
    "Stellar Effective Temperature", # double (average surface temperature of the star [Kelvin])
    "Stellar Radius",                # double (radius of the star [solar radius])
    "Stellar Mass",                  # double (mass of the star [solar masses])
    "Stellar Surface Gravity",       # double (average surface gravity [log g])
    "Distance"                       # double (distance from our system [Parsec])
]
filtered_data = exoplanet_data[selected_columns]

filtered_data.head(5)
    
Out[1]:
Planet Name Planet Host Num Stars Num Planets Discovery Method Discovery Year Discovery Facility Orbital Period Days Orbit Semi-Major Axis Mass Stellar Effective Temperature Stellar Radius Stellar Mass Stellar Surface Gravity Distance
0 11 Com b 11 Com 2 1 Radial Velocity 2007 Xinglong Station 326.03000 1.29 6165.6000 4742.0 19.00 2.70 2.31 93.1846
1 11 UMi b 11 UMi 1 1 Radial Velocity 2009 Thueringer Landessternwarte Tautenburg 516.21997 1.53 4684.8142 4213.0 29.79 2.78 1.93 125.3210
2 14 And b 14 And 1 1 Radial Velocity 2008 Okayama Astrophysical Observatory 185.84000 0.83 1525.5000 4813.0 11.00 2.20 2.63 75.4392
3 14 Her b 14 Her 1 2 Radial Velocity 2002 W. M. Keck Observatory 1773.40002 2.93 1481.0878 5338.0 0.93 0.90 4.45 17.9323
4 16 Cyg B b 16 Cyg B 3 1 Radial Velocity 1996 Multiple Observatories 798.50000 1.66 565.7374 5750.0 1.13 1.08 4.36 21.1397
In [2]:
filtered_data.shape
Out[2]:
(4575, 15)

Basic analysis¶

Number of Discovered Planets per Year¶

In [3]:
planets_per_year = filtered_data['Discovery Year'].value_counts().sort_index()

# Creare un istogramma ridimensionato con proporzioni mantenute
discovery_year_histogram = px.bar(
    planets_per_year,
    x=planets_per_year.index,
    y=planets_per_year.values,
    labels={'x': 'Discovery Year', 'y': 'Number of Planets Discovered'},
    title='Number of Exoplanets Discovered Per Year',
    text_auto=True  # Mostra i valori sopra le barre
)

# Modificare l'asse X per aggiungere tick con linee visibili
discovery_year_histogram.update_layout(
    yaxis=dict(
        type="log",  # Scala logaritmica
        title="Number of Planets Discovered (Log Scale)"
    ),
    xaxis=dict(
        title="Discovery Year",
        tickmode="linear",  # Tick lineari
        dtick=1,  # Tick ogni anno
        ticklen=5,  # Lunghezza delle righe dei tick
        tickwidth=1,  # Spessore delle righe dei tick
        tickvals=planets_per_year.index,  # Mostra righe dei tick per ogni anno
        showline=True,  # Mostra la linea dell'asse
        showgrid=False  # Disabilita le linee della griglia
    ),
    title=dict(
        x=0.5  # Centrare il titolo
    ),
    width=1280,  # Larghezza della figura (ridotta per powerpioint)
    height=720
)

# Visualizzare il grafico
discovery_year_histogram.show()

Number of Stars and Planets per System¶

In [4]:
# Contare la frequenza di stelle per sistema
stars_per_system = filtered_data['Num Stars'].value_counts()

# Contare la frequenza di pianeti per sistema
planets_per_system = filtered_data['Num Planets'].value_counts()

# Creare la sottotrama
fig = sp.make_subplots(
    rows=1, cols=2, 
    subplot_titles=['Number of Stars per System', 'Number of Planets per System']
)

# Aggiungere il grafico istogramma per il numero di stelle per sistema
fig.add_trace(
    go.Bar(
        x=stars_per_system.index, 
        y=stars_per_system.values, 
        name="Stars per System"
    ), 
    row=1, col=1
)

# Aggiungere il grafico istogramma per il numero di pianeti per sistema
fig.add_trace(
    go.Bar(
        x=planets_per_system.index, 
        y=planets_per_system.values, 
        name="Planets per System"
    ), 
    row=1, col=2
)

# Modificare il layout
fig.update_layout(
    title_text="Distribution of the Number of Stars and Planets per System",
    showlegend=False,  # Rimuove la legenda
    width=1280,  # Larghezza per slide PowerPoint
    height=720   # Altezza per slide PowerPoint
)

# Impostare l'asse Y in scala logaritmica per entrambi i grafici
fig.update_yaxes(type="log", row=1, col=1)  # Grafico di sinistra
fig.update_yaxes(type="log", row=1, col=2)  # Grafico di destra

# Configurare i tick sull'asse X per mostrare solo valori interi
fig.update_xaxes(
    tickmode="array",
    tickvals=stars_per_system.index,
    ticktext=[str(int(x)) for x in stars_per_system.index],  # Mostra solo valori interi come etichette
    row=1, col=1
)

fig.update_xaxes(
    tickmode="array",
    tickvals=planets_per_system.index,
    ticktext=[str(int(x)) for x in planets_per_system.index],  # Mostra solo valori interi come etichette
    row=1, col=2
)

# Visualizzare il grafico
fig.show()

Top 5 most and least massive exoplanets¶

In [5]:
# Trovare i 5 pianeti più massicci e i 5 meno massicci
most_massive_planets = filtered_data.nlargest(5, "Mass")[["Planet Name", "Mass"]]
least_massive_planets = filtered_data.nsmallest(5, "Mass")[["Planet Name", "Mass"]]

# Creare la sottotrama
fig = sp.make_subplots(
    rows=1, cols=2, 
    subplot_titles=["5 Most Massive Planets", "5 Least Massive Planets"],
    horizontal_spacing=0.2
)

# Aggiungere il grafico per i 5 pianeti più massicci (asse invertito)
fig.add_trace(
    go.Bar(
        y=most_massive_planets["Planet Name"], 
        x=most_massive_planets["Mass"], 
        name="Most Massive Planets",
        orientation="h"  # Orientamento orizzontale
    ),
    row=1, col=1
)

# Aggiungere il grafico per i 5 pianeti meno massicci (asse invertito)
fig.add_trace(
    go.Bar(
        y=least_massive_planets["Planet Name"], 
        x=least_massive_planets["Mass"], 
        name="Least Massive Planets",
        orientation="h"  # Orientamento orizzontale
    ),
    row=1, col=2
)

# Modificare il layout per PowerPoint
fig.update_layout(
    title_text="Most and Least Massive Planets",
    showlegend=False,
    yaxis_title="Planet Name (Most Massive)",
    xaxis_title="Mass [Earth Masses]",
    yaxis2_title="Planet Name (Least Massive)",
    xaxis2_title="Mass [Earth Masses]",
    width=1280,  # Larghezza per PowerPoint
    height=720   # Altezza per PowerPoint
)

# Allineare meglio le etichette
fig.update_yaxes(autorange="reversed", row=1, col=1)  # Ordinamento inverso per migliorare leggibilità
fig.update_yaxes(autorange="reversed", row=1, col=2)

# Visualizzare il grafico
fig.show()

Top 5 most and least massive stars¶

In [6]:
# Filtrare i dati per escludere valori nulli
filtered_data_non_null = filtered_data.dropna(subset=["Stellar Radius"])

# Trovare i valori di raggio più piccolo e più grande
min_radius = filtered_data_non_null["Stellar Radius"].min()
max_radius = filtered_data_non_null["Stellar Radius"].max()
sun_radius = 1  # Raggio del Sole (unità di misura)

# Creare un DataFrame con i dati per il confronto
radius_comparison = pd.DataFrame({
    "Star": ["Smallest Star", "Sun (Reference)", "Largest Star"],
    "Radius [Solar Radii]": [min_radius, sun_radius, max_radius],
    "Radius for Plot": [min_radius * 10, sun_radius * 10, max_radius * 10]  # Ingrandire per visibilità
})

# Creare il grafico scatter con cerchi proporzionali al raggio
fig = px.scatter(
    radius_comparison,
    x=["Smallest Star", "Sun (Reference)", "Largest Star"],  # Posizioni fisse sull'asse X
    y=[0, 0, 0],  # Tutti i punti sono sulla stessa linea (visivamente centrati)
    size="Radius for Plot",  # Proporzionalità del cerchio
    size_max=250,  # Limite massimo per i cerchi
    color_discrete_sequence=["orange"],  # Colore arancione per i cerchi
    labels={"x": "Star dimension", "size": "Stellar Radius (Solar Radii)"},
    title="Comparison of Stellar sizes: Smallest Star, Sun, Largest Star",
    template="plotly_white"
)

# Personalizzare il layout
fig.update_layout(
    width=1280,  # Dimensioni per slide PowerPoint
    height=720,
    showlegend=False,  # Rimuovere legenda
    yaxis=dict(visible=False),  # Nascondere l'asse Y
    xaxis_title="Stars",
    xaxis=dict(showgrid=False)  # Nascondere la griglia
)

# Mostrare il grafico
fig.show()

Discovery methods analysis¶

Discovery Methods over time (min 10 entries)¶

In [7]:
# Filtrare i metodi con almeno 5 scoperte totali
method_totals = filtered_data["Discovery Method"].value_counts()
valid_discovery_methods = method_totals[method_totals >= 10].index
filtered_discovery_data = filtered_data[filtered_data["Discovery Method"].isin(valid_discovery_methods)]

# Raggruppare per anno e metodo di scoperta e contare il numero di pianeti scoperti
discovery_trend_filtered = (
    filtered_discovery_data.groupby(["Discovery Year", "Discovery Method"])["Planet Name"]
    .count()
    .reset_index(name="Count")
)

# Creare il grafico a linee con dimensioni per slide PowerPoint
fig = px.line(
    discovery_trend_filtered,
    x="Discovery Year",
    y="Count",
    color="Discovery Method",
    labels={
        "Discovery Year": "Year",
        "Count": "Number of Planets Discovered"
    },
    title="Trend of Discovery Methods Over Time",
    template="plotly_white"
)

# Impostare l'asse Y in scala logaritmica
fig.update_layout(
    yaxis=dict(
        type="log",  # Scala logaritmica
        title="Number of Planets Discovered (Log Scale)"
    ),
    title=dict(
        x=0.5  # Centrare il titolo
    ),
    width=1280,  # Larghezza
    height=720   # Altezza
)

# Visualizzare il grafico
fig.show()

Discovery Methods and Distances¶

In [8]:
from plotly.subplots import make_subplots

# Filtrare i metodi di scoperta con almeno 3 entries
method_counts = filtered_data["Discovery Method"].value_counts()
filtered_methods = method_counts[method_counts >= 3].index

# Filtrare i dati originali per i metodi selezionati
filtered_discovery_data = filtered_data[filtered_data["Discovery Method"].isin(filtered_methods)]

# Conteggio dei metodi dopo il filtraggio e ordinamento in ordine crescente
method_counts_filtered = filtered_discovery_data["Discovery Method"].value_counts().sort_values(ascending=True)

# Creazione del grafico subplot
fig = make_subplots(
    rows=1,
    cols=2,
    subplot_titles=("Number of Planets Discovered by Method", "Distance Distribution by Method"),
    horizontal_spacing=0.15,
    shared_yaxes=True  # Condivide l'asse Y
)

# Primo subplot: Istogramma del numero di pianeti scoperti per metodo
fig.add_trace(
    go.Bar(
        x=method_counts_filtered.values,
        y=method_counts_filtered.index,
        orientation='h',  # Orientazione orizzontale
        text=method_counts_filtered.values,  # Aggiungere il numero effettivo come testo
        textposition='auto',  # Posizionare il testo automaticamente
        name="Number of Planets"
    ),
    row=1,
    col=1
)

# Secondo subplot: Boxplot delle distanze per metodo
fig.add_trace(
    go.Box(
        x=filtered_discovery_data["Distance"],
        y=filtered_discovery_data["Discovery Method"],
        name="Distance Distribution",
        orientation='h',  # Orientazione orizzontale
    ),
    row=1,
    col=2
)

# Impostare l'asse X del primo subplot in scala logaritmica
fig.update_xaxes(
    type="log",  # Scala logaritmica
    title_text="Number of Planets (Log Scale)",
    row=1,
    col=1
)

# Configurazione del layout generale per PowerPoint
fig.update_layout(
    title_text="Discovery Methods Analysis: Number of Planets and Distance Distribution",
    xaxis2_title="Distance [pc]",  # Asse X del secondo subplot
    yaxis_title="Discovery Method",
    showlegend=False,
    height=720,  # Altezza per slide PowerPoint
    width=1280,  # Larghezza per slide PowerPoint
    template="plotly_white"
)

# Mostrare il grafico
fig.show()

Correlation Between Planet Mass and Orbital Distance Across Discovery Methods¶

In [9]:
# Filtrare i metodi di scoperta con almeno 5 entries
method_counts = filtered_data["Discovery Method"].value_counts()
filtered_methods = method_counts[method_counts >= 10].index
data_for_scatter = filtered_data[filtered_data["Discovery Method"].isin(filtered_methods)]
In [10]:
# Creare uno scatter plot formattato per PowerPoint
fig = px.scatter(
    data_for_scatter,
    x="Mass",
    y="Orbit Semi-Major Axis",
    color="Discovery Method",
    labels={
        "Mass": "Planet Mass [Earth Masses]",
        "Orbit Semi-Major Axis": "Planet Orbit Semi-Major Axis [AU]"
    },
    title="Planet Mass vs Planet Orbit Semi-Major Axis",
    template="plotly_white",
    log_x=True,  # Scala logaritmica sull'asse X
    log_y=True   # Scala logaritmica sull'asse Y
)

# Formattare il grafico per PowerPoint
fig.update_layout(
    width=1280,  # Larghezza per PowerPoint
    height=720   # Altezza per PowerPoint
)

# Mostrare il grafico
fig.show()

Other analysis¶

Top 9 Discovery Facilities by Number of Planets Discovered¶

In [11]:
# Conteggiare il numero di pianeti scoperti da ciascuna struttura
facility_counts = filtered_data['Discovery Facility'].value_counts()

# Separare i primi 10 osservatori
top_10_facilities = facility_counts.head(10)

# Calcolare il totale di "Others" includendo anche "Multiple Observatories"
others_count = facility_counts[10:].sum()
if "Multiple Observatories" in top_10_facilities.index:
    others_count += top_10_facilities["Multiple Observatories"]
    top_10_facilities = top_10_facilities.drop("Multiple Observatories")

# Aggiungere "Others" ai dati
top_10_facilities_with_others = pd.concat([top_10_facilities, pd.Series({"Others": others_count})])

# Ordinare i valori (escludendo temporaneamente "Others")
top_10_facilities_sorted = top_10_facilities_with_others.drop("Others").sort_values(ascending=True)

# Aggiungere "Others" all'inizio
top_10_facilities_sorted = pd.concat([pd.Series({"Others": others_count}), top_10_facilities_sorted])

# Creare l'istogramma
facility_histogram = px.bar(
    top_10_facilities_sorted,
    y=top_10_facilities_sorted.index,
    x=top_10_facilities_sorted.values,
    labels={'x': 'Number of Planets Discovered', 'y': 'Discovery Facility'},
    title='Top Discovery Facilities by Number of Planets Discovered',
    text_auto=True  # Mostra i valori sopra le barre
)

# Aggiungere opzioni per migliorare la leggibilità
facility_histogram.update_layout(
    yaxis_title="Discovery Facility",
    xaxis_title="Number of Planets Discovered",
    template="plotly_white",  # Tema chiaro
    width=1280,  # Larghezza per slide PowerPoint
    height=720   # Altezza per slide PowerPoint
)

# Lista di osservatori non terrestri
custom_space_facilities = ["Kepler", "K2", "Transiting Exoplanet Survey Satellite (TESS)"]

# Colori specifici per le barre
facility_histogram.update_traces(marker_color=[
    'gray' if i == "Others" else 
    '#636EFA' if i in custom_space_facilities else 
    'red' for i in top_10_facilities_sorted.index
])

# Aggiungere la legenda personalizzata
facility_histogram.add_trace(
    go.Scatter(
        x=[None], y=[None],
        mode="markers",
        marker=dict(size=10, color='#636EFA'),
        name="Space Observatories"
    )
)

facility_histogram.add_trace(
    go.Scatter(
        x=[None], y=[None],
        mode="markers",
        marker=dict(size=10, color='red'),
        name="Ground-based Observatories"
    )
)

# Visualizzare il grafico
facility_histogram.show()

Orbital Period vs Semi-Major Axis Colored by Planet Mass¶

In [12]:
# Filtrare i dati per escludere valori nulli nella colonna della massa
filtered_data_non_null = filtered_data.dropna(subset=["Orbit Semi-Major Axis", "Orbital Period Days", "Mass"])

# Creare una colonna personalizzata per limitare la massa a 2000 per visualizzare meglio il gradiente
filtered_data_non_null["Mass (Capped)"] = filtered_data_non_null["Mass"].apply(lambda x: min(x, 2000))

# Creare uno scatter plot per confrontare distanza e periodo orbitale con gradiente basato sulla massa limitata
fig = px.scatter(
    filtered_data_non_null,
    x="Orbit Semi-Major Axis",
    y="Orbital Period Days",
    color="Mass (Capped)",  # Usare la massa limitata per il gradiente
    color_continuous_scale="Viridis",  # Scala colore continua
    labels={
        "Orbit Semi-Major Axis": "Orbit Semi-Major Axis [AU]",
        "Orbital Period Days": "Orbital Period [Days]",
        "Mass (Capped)": "Planet Mass"
    },
    title="Orbital Period vs Semi-Major Axis Colored by Planet Mass",
    template="plotly_white",
    log_x=True,  # Scala logaritmica sull'asse X
    log_y=True   # Scala logaritmica sull'asse Y
)

# Formattare il grafico per slide PowerPoint
fig.update_layout(
    width=1280,
    height=720 
)

# Mostrare il grafico
fig.show()

Relationship Between Stellar Surface Gravity and Mass with Stellar Radius Highlighted¶

In [13]:
# Filtrare i dati per escludere valori nulli
filtered_data_non_null = filtered_data.dropna(subset=["Stellar Radius", "Stellar Mass", "Stellar Surface Gravity"])

# Creare una colonna personalizzata per limitare il raggio a 5
filtered_data_non_null["Stellar Radius (Capped)"] = filtered_data_non_null["Stellar Radius"].apply(lambda x: min(x, 5))

# Creare il grafico scatter
fig = px.scatter(
    filtered_data_non_null,
    x="Stellar Mass",
    y="Stellar Surface Gravity",
    color="Stellar Radius (Capped)",  # Utilizza il raggio limitato come scala colore
    labels={
        "Stellar Mass": "Stellar Mass [Solar Masses]",
        "Stellar Surface Gravity": "Stellar Surface Gravity (log g)",
        "Stellar Radius (Capped)": "Stellar Radius"
    },
    title="Stellar Surface Gravity vs Stellar Mass with Stellar Radius",
    template="plotly_white",
    color_continuous_scale="Jet"  # Utilizza una scala di colori
)

# Configurare l'intervallo degli assi
fig.update_layout(
    yaxis=dict(
        range=[0, 6],  # Limita l'intervallo tra 0 e 6
        title="Stellar Surface Gravity (log g)"
    ),
    xaxis=dict(
        range=[0, 5],  # Limita l'intervallo tra 0 e 5
        title="Stellar Mass [Solar Masses]"
    ),
    width=1280,  # Larghezza per PowerPoint
    height=720   # Altezza per PowerPoint
)

fig.update_coloraxes(
    colorbar_title="Stellar Radius [solar R]",
    cmin=filtered_data_non_null["Stellar Radius (Capped)"].min(),  # Valore minimo
    cmax=5,  # Limita il valore massimo a 5
    colorbar=dict(
        tickvals=[filtered_data_non_null["Stellar Radius (Capped)"].min(), 1, 2, 3, 4, 5],  # Tick personalizzati
        ticktext=["0", "1", "2", "3", "4", "5+"]
    )
)

# Visualizzare il grafico
fig.show()